﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using Kokomo.Mirror.CliMetadata.Signatures;

namespace Kokomo.Mirror.CliMetadata
{
	public class MetadataReader
	{
		public Stream Stream { get; private set; }
		public MetadataContainer Container { get; private set; }
		public BinaryReader BinaryReader { get; private set; }
		public MetadataReaderOptions Options { get; private set; }

		public MetadataReader(
			Stream stream,
			MetadataContainer container,
			MetadataReaderOptions options = MetadataReaderOptions.None
			)
		{
			if (stream == null) throw new ArgumentNullException("stream");
			if (container == null) throw new ArgumentNullException("container");

			this.Options = options;
			this.Stream = stream;
			this.Container = container;
			this.BinaryReader = new BinaryReader(stream);
		}

		private Action<MetadataReader, IMetadataRow> GetRowReaderDelegate(
			MetadataTable table
			)
		{
			if (table == null) throw new ArgumentNullException("table");

			Type rowType = table.RowType;
			MetadataRowReaderBuilder builder = new MetadataRowReaderBuilder(
				this,
				this.Container,
				rowType
				);

			PropertyInfo[] properties = rowType.GetProperties();
			foreach (var property in properties)
			{
				builder.EmitReadProperty(property);
			}

			return builder.GetDelegate();
		}

		internal StringRef ReadShortStringRef()
		{
			int heapIndex = this.BinaryReader.ReadUInt16();
			//string str = this.Container.GetString(heapIndex);
			return new StringRef(heapIndex);
		}

		internal StringRef ReadLongStringRef()
		{
			int heapIndex = this.BinaryReader.ReadInt32();
			//string str = this.Container.GetString(heapIndex);
			return new StringRef(heapIndex);
		}

		internal BlobRef ReadShortBlobRef()
		{
			int heapIndex = this.BinaryReader.ReadUInt16();
			//byte[] data = this.Container.GetBlob(heapIndex);
			return new BlobRef(heapIndex);
		}

		internal BlobRef ReadLongBlobRef()
		{
			int heapIndex = this.BinaryReader.ReadInt32();
			//byte[] data = this.Container.GetBlob(heapIndex);
			return new BlobRef(heapIndex);
		}

		internal SignatureRef ReadShortSignatureRef(SignatureKind kind)
		{
			int heapIndex = this.BinaryReader.ReadUInt16();
			//Signature signature = this.Container.GetSignature(heapIndex, kind);
			return new SignatureRef(heapIndex, kind);
		}

		internal SignatureRef ReadLongSignatureRef(SignatureKind kind)
		{
			int heapIndex = this.BinaryReader.ReadInt32();
			//Signature signature = this.Container.GetSignature(heapIndex, kind);
			return new SignatureRef(heapIndex, kind);
		}

		//internal static TypeSpecRef ReadShortTypeSpecRef(MetadataReader reader)
		//{
		//    int heapIndex = reader.BinaryReader.ReadUInt16();
		//    TypeSpec typeSpec = reader.Container.GetTypeSpec(heapIndex);
		//    return new TypeSpecRef(heapIndex, typeSpec);
		//}

		//internal static TypeSpecRef ReadLongTypeSpecRef(MetadataReader reader)
		//{
		//    int heapIndex = reader.BinaryReader.ReadInt32();
		//    TypeSpec typeSpec = reader.Container.GetTypeSpec(heapIndex);
		//    return new TypeSpecRef(heapIndex, typeSpec);
		//}

		internal static GuidRef ReadShortGuidRef(MetadataReader reader)
		{
			int heapIndex = reader.BinaryReader.ReadUInt16();
			Guid guid = reader.Container.GetGuid(heapIndex);
			return new GuidRef(heapIndex, guid);
		}

		internal static GuidRef ReadLongGuidRef(MetadataReader reader)
		{
			int heapIndex = reader.BinaryReader.ReadInt32();
			Guid guid = reader.Container.GetGuid(heapIndex);
			return new GuidRef(heapIndex, guid);
		}

		internal static CodedIndex ReadShortIndex(MetadataReader reader, MetadataTableNumber tableNumber)
		{
			int index = reader.BinaryReader.ReadUInt16();
			return new CodedIndex(tableNumber, index);
		}

		internal static CodedIndex ReadLongIndex(MetadataReader reader, MetadataTableNumber tableNumber)
		{
			int index = reader.BinaryReader.ReadInt32();
			return new CodedIndex(tableNumber, index);
		}

		internal static CodedIndex ReadShortIndex(MetadataReader reader, CodedIndexType indexType)
		{
			int value = reader.BinaryReader.ReadUInt16();

			CodedIndex index;
			if (reader.Options.HasFlag(MetadataReaderOptions.AllowInvalidIndex))
				CodedIndex.TryParseRawValue(value, indexType, out index);
			else
				index = CodedIndex.Parse(value, indexType);

			return index;
		}

		internal static CodedIndex ReadLongIndex(MetadataReader reader, CodedIndexType indexType)
		{
			int value = reader.BinaryReader.ReadInt32();

			CodedIndex index;
			if (reader.Options.HasFlag(MetadataReaderOptions.AllowInvalidIndex))
				CodedIndex.TryParseRawValue(value, indexType, out index);
			else
				index = CodedIndex.Parse(value, indexType);

			return index;
		}

		public void ReadTable(MetadataTable table)
		{
			if (table == null) throw new ArgumentNullException("table");

			Action<MetadataReader, IMetadataRow> rowReader = this.GetRowReaderDelegate(table);

			int rowCount = table.RowCount;
			for (int i = 0; i < rowCount; i++)
			{
				IMetadataRow row = (IMetadataRow)table.GetItem(i);
				rowReader(this, row);
			}
		}
		//public void ReadTable(MetadataTable table)
		//{
		//    if (table == null) throw new ArgumentNullException("table");

		//    Type rowType = table.RowType;
		//    PropertyInfo[] properties = rowType.GetProperties();

		//    int rowCount = table.RowCount;
		//    for (int i = 0; i < rowCount; i++)
		//    {
		//        object row = table[i];

		//        foreach (var property in properties)
		//        {
		//            object fieldValue = this.ReadFieldValue(property);
		//            property.SetValue(row, fieldValue, null);
		//        }
		//    }
		//}

		private object ReadPrimitive(Type type)
		{
			if ((object)type == null) throw new ArgumentNullException("type");

			BinaryReader reader = new BinaryReader(this.Stream);
			object value = null;
			if (type == typeof(short))
			{
				value = reader.ReadInt16();
			}
			else if (type == typeof(int))
			{
				value = reader.ReadInt32();
			}
			else if (type == typeof(byte))
			{
				value = reader.ReadByte();
			}
			else
			{
				throw new InvalidOperationException();
			}

			return value;
		}
		//private object ReadFieldValue(PropertyInfo property)
		//{
		//    if (property == null) throw new ArgumentNullException("property");

		//    Type fieldType = property.PropertyType;
		//    object value = null;
		//    BinaryReader reader = new BinaryReader(this.Stream);
		//    if (fieldType == typeof(StringRef))
		//    {
		//        int heapIndex;
		//        if (this.Container.HasLargeStringHeap)
		//        {
		//            heapIndex = reader.ReadInt32();
		//        }
		//        else
		//        {
		//            heapIndex = reader.ReadUInt16();
		//        }

		//        string str = this.Container.GetString(heapIndex);
		//        value = new StringRef(heapIndex, str);

		//    }
		//    else if (fieldType == typeof(BlobRef))
		//    {
		//        int heapIndex;
		//        if (this.Container.HasLargeBlobHeap)
		//        {
		//            heapIndex = reader.ReadInt32();
		//        }
		//        else
		//        {
		//            heapIndex = reader.ReadUInt16();
		//        }

		//        value = new BlobRef(heapIndex, this.Container.GetBlob(heapIndex));
		//    }
		//    else if (fieldType == typeof(GuidRef))
		//    {
		//        int heapIndex;
		//        if (this.Container.HasLargeGuidHeap)
		//        {
		//            heapIndex = reader.ReadInt32();
		//        }
		//        else
		//        {
		//            heapIndex = reader.ReadUInt16();
		//        }

		//        value = new GuidRef(heapIndex, this.Container.GetGuid(heapIndex));
		//    }
		//    else if (fieldType == typeof(CodedIndex))
		//    {
		//        SimpleIndexAttribute simpleIndexAttribute = property.GetCustomAttribute<SimpleIndexAttribute>();
		//        if (simpleIndexAttribute != null)
		//        {
		//            int maxRows = this.Container.GetRowCount(simpleIndexAttribute.TableNumber);
		//            int index;
		//            if (maxRows < 0x10000)
		//            {
		//                index = reader.ReadUInt16();
		//            }
		//            else
		//            {
		//                index = reader.ReadInt32();
		//            }

		//            value = new CodedIndex(simpleIndexAttribute.TableNumber, index);
		//        }
		//        else
		//        {
		//            CodedIndexAttribute attribute = property.GetCustomAttribute<CodedIndexAttribute>();
		//            if (attribute == null) throw new InvalidOperationException("Invalid field");

		//            int selectorBits = Log2(attribute.TableNumbers.Length - 1);
		//            int maxTableRows = 0;
		//            foreach (var tableNumberOption in attribute.TableNumbers)
		//            {
		//                if (tableNumberOption != MetadataTableNumber.Invalid)
		//                {
		//                    int tableRows = this.Container.GetRowCount(tableNumberOption);
		//                    maxTableRows = Math.Max(maxTableRows, tableRows);
		//                }
		//            }
		//            int rowBits = Log2(maxTableRows);

		//            int codedIndexValue;
		//            if ((rowBits + selectorBits) <= 16)
		//            {
		//                codedIndexValue = reader.ReadUInt16();
		//            }
		//            else
		//            {
		//                codedIndexValue = reader.ReadInt32();
		//            }

		//            int tableSelector = codedIndexValue & ((1 << selectorBits) - 1);
		//            int rowIndex = codedIndexValue >> selectorBits;
		//            MetadataTableNumber tableNumber = attribute.TableNumbers[tableSelector];

		//            value = new CodedIndex(tableNumber, rowIndex);
		//        }
		//    }
		//    else if (fieldType.IsEnum)
		//    {
		//        ConstantFieldAttribute constantFieldAttribute = property.GetCustomAttribute<ConstantFieldAttribute>();

		//        Type physicalType = (constantFieldAttribute == null) ? fieldType.GetEnumUnderlyingType() : constantFieldAttribute.FieldType;
		//        object rawValue = this.ReadPrimitive(physicalType);
		//        value = Enum.ToObject(fieldType, rawValue);
		//    }
		//    else
		//    {
		//        value = this.ReadPrimitive(fieldType);
		//    }

		//    return value;
		//}

	}
}
